#  include <GL/gl.h>
#  include <GL/glu.h>
#  include <GL/glut.h>
#include <GL/glx.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <math.h>

unsigned int vindusbredde;
unsigned int vindushoyde;
double syns_bredde, syns_hoyde;
double maks_reps;
double syns_re_min;
double syns_im_min;
double re_faktor;
double im_faktor;
bool beregnet_mandelbrot;
bool **mandelbrot;



void init()
{
  /* set clear color to black */
  glClearColor (0.0, 0.0, 0.0, 0.0);
  
  /* set fill color to white */
  glColor3f(1.0, 1.0, 1.0);
  
  /* set up standard orthogonal view with clipping */
  /* box as cube of side 2 centered at origin */
  /* This is default view and these statement could be removed */
  glMatrixMode (GL_PROJECTION);
  glLoadIdentity ();
  glOrtho(0.0, vindusbredde, 0.0, vindushoyde, 0.0, vindushoyde);
}


bool sjekk_punkt(double xr, double xi, double cr, double ci){
  unsigned int repetisjoner;
  bool er_inne = true;
  for(repetisjoner = 0; repetisjoner < maks_reps; repetisjoner ++){
    double xr2 = xr * xr;
    double xi2 = xi * xi;    
    if(xr2 + xi2 > 4.0){
      er_inne = false;
      break;
    }
    xi = 2 * xr * xi + ci;
    xr = xr2 - xi2 + cr;
  }
  return er_inne;
}


void beregn_mandelbrot(){
  int r, i;
 
  for(r = 0; r < vindusbredde; r++){
    double c_re =  r * re_faktor + syns_re_min;
    for(i = 0; i < vindushoyde; i++){
      double c_im = i * im_faktor + syns_im_min;
      mandelbrot[r][i] = sjekk_punkt(c_re, c_im, c_re, c_im);
    }
  }
}

void tegn_mandelbrot(){
  int r, i;
  glBegin(GL_POINTS);
  for(r = 0; r < vindusbredde; r++){
    for(i = 0; i < vindushoyde; i++){
      if(mandelbrot[r][i])
	glVertex2f(r, i);
    }
  }
  glEnd();
}

GLvoid display(GLvoid){
  if(!beregnet_mandelbrot){
    beregn_mandelbrot();
    beregnet_mandelbrot = true;
  }
  glClear(GL_COLOR_BUFFER_BIT);
  tegn_mandelbrot();
  glFlush();
}


void oppdater_konstanter(){
  maks_reps = 200 * log(log(vindusbredde / syns_bredde));
  syns_hoyde = syns_bredde * vindushoyde / vindusbredde;
  re_faktor = syns_bredde / vindusbredde;
  im_faktor = syns_hoyde / vindushoyde;
}


void zoom(int x, int y){
  double nytt_senter_re = x * re_faktor + syns_re_min;
  double  nytt_senter_im = syns_im_min + syns_hoyde - y * im_faktor;
  oppdater_konstanter();
  syns_re_min = nytt_senter_re - syns_bredde / 2.0;
  syns_im_min = nytt_senter_im - syns_hoyde / 2.0;
  beregnet_mandelbrot = false;
  display();
}

void zoom_ut(int x, int y){
  syns_bredde *= 2.0;
  zoom(x, y);
}


void zoom_inn(int x, int y){
  syns_bredde /= 2.0;
  zoom(x, y);
}

void knappTrykk(int button, int state, int x, int y)
{
  if(state == GLUT_UP){
    switch(button){
    case GLUT_LEFT_BUTTON :
      zoom_inn(x, y);
      break;
    case GLUT_RIGHT_BUTTON:
      zoom_ut(x, y);
      break;
    }
  }
}

/**
   Kalles en gang fra main i starten

   Setter opp alle konstanter som trengs med gode start-verdier
**/
GLvoid init_verdier(){
  unsigned int i;
  syns_bredde = 2.0;
  syns_hoyde = 1.5;
  syns_re_min = -1.5;
  syns_im_min = -1.0;
  vindusbredde = 500;
  vindushoyde = 500;
  mandelbrot = calloc(vindusbredde, sizeof(bool*));
  for(i = 0; i < vindusbredde; i++)
    mandelbrot[i] = calloc(vindushoyde, sizeof(bool));
  oppdater_konstanter();
}
  
/**
   Kalles når størrelsen på skjermen endres
**/
void endre_skjerm(int w, int h)
{
  unsigned int i;
  if(w < vindusbredde){
    for(i = w; i < vindusbredde; i++)
      free(mandelbrot[i]);
  }
  mandelbrot = realloc(mandelbrot, w * sizeof(bool*));
  for(i = 0; i < vindusbredde && i < w; i++)
    mandelbrot[i] = realloc(mandelbrot[i], h * sizeof(bool));
  for(i = vindusbredde; i < w; i++)
    mandelbrot[i] = calloc( h,  sizeof(bool));
  syns_bredde = (syns_bredde * w) / (double) vindusbredde;
  vindusbredde = w;
  vindushoyde = h;
  glViewport(0,0,vindusbredde, vindushoyde);
  glLoadIdentity ();
  glOrtho(0.0, vindusbredde, 0.0, vindushoyde, 0.0, vindushoyde);
  oppdater_konstanter();
  beregnet_mandelbrot = false;
  display();
}

/**
   Kalles når en pil-tast trykkes
   Flytter fraktalen til høyre og venstre
**/
void pilTrykk(int key, int x, int y){
  switch(key){
  case GLUT_KEY_LEFT: /* venstre pil */
    syns_re_min -= syns_bredde / 4.0;
    break;
  case GLUT_KEY_UP: /* opp pil */
    syns_im_min += syns_hoyde / 4.0;
    break;
  case GLUT_KEY_RIGHT: /* høyre pil */
    syns_re_min += syns_bredde / 4.0;
    break;
  case GLUT_KEY_DOWN: /* ned pil */
    syns_im_min -= syns_hoyde / 4.0;
    break;
  }
  oppdater_konstanter();
  beregnet_mandelbrot = false;
  display();
}


/**
   Kalles når en vanlig tastaturknapp trykkes
   Zoomer inn og ut med + og -
**/
void tasteTrykk(unsigned char key, int x, int y){
  switch(key){
  case '+': /* zoome inn på + */
    zoom_inn(vindusbredde / 2, vindushoyde / 2);
    break;
  case '-': /* zoome ut */
    zoom_ut(vindusbredde / 2, vindushoyde / 2);
    break;
  case 'q':
    exit(EXIT_SUCCESS);
    break;
  }
}


int main(int argc, char** argv) {
  init_verdier();
  glutInit(&argc,argv);
  glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
  glutInitWindowSize(vindusbredde,vindushoyde);
  glutInitWindowPosition(0,0);
  glutCreateWindow("simple");
  glutReshapeFunc(endre_skjerm);
  glutMouseFunc(knappTrykk);
  glutSpecialFunc(pilTrykk);
  glutKeyboardFunc(tasteTrykk);
  glutDisplayFunc(display);
  init();
  glutMainLoop();
  return 0;
}

